#include <zenoui/widgets/GraphicsGraph.hpp>
#include <zenoui/widgets/LabelWidget.hpp>
#include <zenoui/draws/BezierCurveDraw.hpp>
#include <zenoui/check_gl.hpp>
#include <zenoui/Enums.hpp>
#include <zenox/unicode.hpp>
#include <zenox/minilog.hpp>

namespace zenoui {

using zenom::GraphRole;
using zenom::GraphModel;
using zenom::NodeRole;
using zenom::NodeModel;
using zenom::PortRole;
using zenom::PortModel;

ZENO_HOT_FN void GraphicsNode::paint() {
    auto myrect = rect();

    glBegin(GL_QUADS);
    glColor4fv(glm::value_ptr(m_background_color));
    glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y);
    glVertex2f(myrect.m_topleft.x + myrect.m_size.x, myrect.m_topleft.y);
    glVertex2f(myrect.m_topleft.x + myrect.m_size.x, myrect.m_topleft.y + myrect.m_size.y);
    glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y + myrect.m_size.y);
    CHECK_GL(glEnd());

    if (m_selected) {
        CHECK_GL(glLineWidth(2.0f));
        glBegin(GL_LINE_STRIP);
        glm::vec4 border_color(0.8f, 0.9f, 0.9f, 0.6f);
        glColor4fv(glm::value_ptr(border_color));
        glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y);
        glVertex2f(myrect.m_topleft.x + myrect.m_size.x, myrect.m_topleft.y);
        glVertex2f(myrect.m_topleft.x + myrect.m_size.x, myrect.m_topleft.y + myrect.m_size.y);
        glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y + myrect.m_size.y);
        glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y);
        CHECK_GL(glEnd());
    }
}

GraphicsNode::GraphicsNode() {
    modify_shape([&] (Shape &sp) {
        sp.size = {k_node_size.x, k_node_size.y};
        sp.layout = AbsoluteLayout;
        sp.scissor = ScissorParentOnly;
        sp.event_margin = GraphicsPort::k_port_size.y;
    });

    BackgroundColorProperty::set(this, {0.5f, 0.4f, 0.3f, 1.0f});

    m_label = add_child<LabelWidget>();
    /* m_label->modify_shape([&] (Shape &sp) { */
    /*     sp.scissor = ScissorParentOnly; */
    /* }); */
    TextAlignmentProperty::set(m_label, TextAlignCenter);
    /* TextProperty::set(m_label, U"无标题节点"); */
    FontSizeProperty::set(m_label, 28.0f);
    auto clr = FontColorProperty::get(m_label);
    clr.a = 0.3f;
    FontColorProperty::set(m_label, clr);
}

GraphicsNode::~GraphicsNode() = default;

ZENO_HOT_FN void GraphicsNode::handle(MouseHoverEvent *event) {
    if (event->is_enter()) {
        auto clr = FontColorProperty::get(m_label);
        clr.a = 0.6f;
        FontColorProperty::set(m_label, clr);
    } else if (event->is_leave()) {
        auto clr = FontColorProperty::get(m_label);
        clr.a = 0.3f;
        FontColorProperty::set(m_label, clr);
    }
    event->accept(this);
}

void GraphicsNode::set_model(NodeModel *model) {
    m_model = model;
    model->view = this;
}

void GraphicsNode::on_update(NodeRole role, std::any value) {
    switch (role) {
        case NodeRole::NodeType: {
        } break;
        case NodeRole::NodeTitle: {
            TextProperty::set(m_label, zenox::u8tou32(std::any_cast<std::string>(value)));
            auto lblsize = m_label->label_rect().size();
            m_label->modify_shape([&] (Shape &sp) {
                sp.size = {lblsize.x, lblsize.y};
                sp.position = {k_node_size.x + 8, (k_node_size.y - lblsize.y) / 2};
            });
        } break;
        case NodeRole::NodePortCount: {
            auto new_size = std::any_cast<size_t>(value);
            for (auto port: m_ports) {
                /* printf("2 %p %p\n", (void *)this, (void *)port); */
                remove_child(port);
            }
            m_ports.clear();
            m_ports.resize(new_size);
            for (size_t n = m_ports.size(), i = 0; i < n; i++) {
                auto &port = m_ports[i];
                port = add_child<GraphicsPort>();
                port->m_parent_node = this;
                port->set_model(&m_model->ports[i]);
                port->modify_shape([&] (Shape &sp) {
                    /* sp.size = {GraphicsPort::k_port_size.x, GraphicsPort::k_port_size.y}; */
                    sp.position = {
                        k_node_size.x * (i + 0.5f) / n - GraphicsPort::k_port_size.x / 2,
                        -GraphicsPort::k_port_size.y};
                });
            }
        } break;
    }
}

void GraphicsNode::handle(MouseClickEvent *event) {
    if (event->button() == MouseRightButton) {
        auto graph = static_cast<GraphicsGraph *>(parent());
        if (graph->m_pending_link_out_port) {
            ZENO_ASSERT(!graph->m_pending_link_in_node);
            if (graph->m_pending_link_out_port->m_linked_in_node)
                graph->m_pending_link_out_port->model()->unlink();
            if (graph->m_pending_link_out_port->m_parent_node != this) [[likely]]
                graph->m_pending_link_out_port->model()->link(model());
            graph->m_pending_link_out_port = nullptr;
        } else {
            // if (graph->m_pending_link_in_node) delete_pending_link();
            graph->m_pending_link_in_node = zenox::weak_from(this);
        }
        event->accept(this);
    }
    GraphicsItemWidget::handle(event);
}

void GraphicsPort::paint() {
    EmptyWidget::paint();
}

GraphicsPort::GraphicsPort() {
    modify_shape([&] (Shape &sp) {
        sp.size = {k_port_size.x, k_port_size.y};
        sp.layout = AbsoluteLayout;
        sp.scissor = ScissorParentOnly;
    });

    BackgroundColorProperty::set(this, {0.9f, 0.6f, 0.2f, 0.8f});

    m_label = add_child<LabelWidget>();
    /* m_label->modify_shape([&] (Shape &sp) { */
    /*     sp.scissor = ScissorParentOnly; */
    /* }); */
    /* TextProperty::set(m_label, U"无标题端口"); */
    FontSizeProperty::set(m_label, 28.0f);
    auto clr = FontColorProperty::get(m_label);
    clr.a = 0.0f;
    FontColorProperty::set(m_label, clr);
}

GraphicsPort::~GraphicsPort() = default;

ZENO_HOT_FN void GraphicsPort::handle(MouseHoverEvent *event) {
    if (event->is_enter()) {
        auto clr = FontColorProperty::get(m_label);
        clr.a = 0.3f;
        FontColorProperty::set(m_label, clr);
        /* event->send_to(m_parent_node); */
    } else if (event->is_leave()) {
        auto clr = FontColorProperty::get(m_label);
        clr.a = 0.0f;
        FontColorProperty::set(m_label, clr);
        /* event->send_to(m_parent_node); */
    }
    event->accept(this);
}

void GraphicsPort::handle(MouseClickEvent *event) {
    if (event->button() == MouseRightButton) {
        auto node = static_cast<GraphicsNode *>(parent());
        auto graph = static_cast<GraphicsGraph *>(node->parent());
        if (graph->m_pending_link_in_node) {
            ZENO_ASSERT(!graph->m_pending_link_out_port);
            if (m_linked_in_node)
                model()->unlink();
            if (m_parent_node != graph->m_pending_link_in_node) [[likely]]
                model()->link(graph->m_pending_link_in_node->model());
            graph->m_pending_link_in_node = nullptr;
        } else {
            /* if (graph->m_pending_link_out_port) */
            /*     graph->m_pending_link_out_port->unlink(); */
            graph->m_pending_link_out_port = zenox::weak_from(this);
        }
        event->accept(this);
    }
}

void GraphicsPort::set_model(PortModel *model) {
    m_model = model;
    model->view = this;
}

void GraphicsPort::on_link(NodeModel *node_model) {
    auto that_node = static_cast<GraphicsNode *>(node_model->view);
    ZENO_ASSERT(m_parent_node != that_node, _, "loop detected");
    ZENO_ASSERT(!m_linked_in_node);
    ZENO_ASSERT(!m_linked_link);
    ZENO_ASSERT(that_node->m_linked_out_ports.insert(this).second);
    auto node = static_cast<GraphicsNode *>(parent());
    auto graph = static_cast<GraphicsGraph *>(node->parent());
    auto link = graph->add_child<GraphicsLink>();
    /* printf("%p %p\n", (void *)graph, (void *)link); */
    m_linked_in_node = that_node;
    m_linked_link = link;
    link->m_in_port = zenox::weak_from(this);
    auto invalidator = [in_port = zenox::weak_from(this), out_node = zenox::weak_from(that_node)] (GraphicsLink *link) {
        ZENO_ASSERT(in_port);
        ZENO_ASSERT(out_node);
        auto in_pos = static_cast<GraphicsNode *>(in_port->parent())->position()
            + in_port->shape().position + GraphicsPort::k_port_size * glm::vec2(0.5f, 0.0f);
        auto out_pos = out_node->position() + GraphicsNode::k_node_size * glm::vec2(0.5f, 1.0f);
        link->update_in_out_pos(in_pos, out_pos);
    };
    invalidator(link);
    m_parent_node->sig_updatePosition.connect(zenox::weak_from(link), invalidator);
    that_node->sig_updatePosition.connect(zenox::weak_from(link), invalidator);
}

void GraphicsPort::on_unlink(NodeModel *node_model) {
    ZENO_ASSERT(node_model);
    ZENO_ASSERT(m_linked_in_node);
    ZENO_ASSERT(m_linked_link);
    ZENO_ASSERT(static_cast<GraphicsNode *>(node_model->view) == m_linked_in_node);
    /* printf("%p\n", (void *)this); */
    /* for (auto p: m_linked_in_node->m_linked_out_ports) printf("- %p\n", (void *)p); */
    ZENO_ASSERT(m_linked_in_node->m_linked_out_ports.erase(this));
    auto node = static_cast<GraphicsNode *>(parent());
    auto graph = static_cast<GraphicsGraph *>(node->parent());
    /* printf("1 %p %p\n", (void *)graph, (void *)m_linked_link); */
    graph->remove_child(m_linked_link);
    m_linked_in_node = nullptr;
    m_linked_link = nullptr;
}

void GraphicsPort::on_update(PortRole role, std::any value) {
    switch (role) {
        case PortRole::PortName: {
            TextProperty::set(m_label, zenox::u8tou32(std::any_cast<std::string>(value)));
            auto lblsize = m_label->label_rect().size();
            m_label->modify_shape([&] (Shape &sp) { // TODO: really gotta refactor this stupid update_rect system
                sp.size = {lblsize.x, lblsize.y};
                sp.position = {(k_port_size.x - lblsize.x) / 2, -lblsize.y - 8};
            });
        } break;
        case PortRole::PortDataType: {
        } break;
        case PortRole::PortInlineValue: {
        } break;
        case PortRole::PortVisible: {
        } break;
    }
}

GraphicsLink::GraphicsLink() {
    m_draggable = false;
    modify_shape([&] (Shape &sp) {
        sp.zvalue = 1.0f; // link is displayed above all other nodes
        sp.scissor = ScissorParentOnly;
    });
}

GraphicsLink::~GraphicsLink() = default;

ZENO_HOT_FN void GraphicsLink::update_in_out_pos(glm::vec2 const &p1, glm::vec2 const &p2) {
    m_in_pos = p1;
    m_out_pos = p2;
    m_baked_curve = std::nullopt;
}

ZENO_HOT_FN BezierCurveDrawBaked &GraphicsLink::get_curve() const {
    if (!m_baked_curve) {
        auto [in_pos, out_pos] = in_out_pos();
        auto cpOffset = glm::vec2(0, k_link_curveness);
        BezierCurveDraw curve{in_pos, out_pos, in_pos - cpOffset, out_pos + cpOffset, 32, k_link_width};
        m_baked_curve = curve.bake();
    }
    return *m_baked_curve;
}

ZENO_HOT_FN void GraphicsLink::paint() {
    auto color = glm::vec4(0.5f, 0.6f, 0.7f, 0.8f);
    if (m_selected)
        color = glm::vec4(0.8f, 0.9f, 0.9f, 0.8f);
    get_curve().paint(color);
}

ZENO_HOT_FN bool GraphicsLink::contains_point(glm::vec2 const &pos) const {
    return get_curve().contains(pos);
}

/* ZENO_HOT_FN void GraphicsLink::handle(MouseHoverEvent *event) { */
/*     if (!contains_point(event->pos())) [[likely]] return; */
/*     event->accept(this); */
/* } */

void GraphicsLink::handle(MouseClickEvent *event) {
    if (!contains_point(event->pos())) [[likely]] return;
    GraphicsItemWidget::handle(event);
}

void GraphicsGraph::handle(MouseClickEvent *event) {
    GraphicsViewWidget::handle(event);
}

void GraphicsGraph::handle(KeyPressEvent *event) {
    switch (event->key()) {
        case GLFW_KEY_DELETE: {
            zenox::log_trace("deleting selected {} node(s)", m_selected_items.size());
            for (auto const &item: m_selected_items) {
                auto pitem = item.get();
                if (!pitem) [[unlikely]] continue;
                if (auto node = dynamic_cast<GraphicsNode *>(pitem)) {
                    model()->delete_node(node->model());
                } else if (auto link = dynamic_cast<GraphicsLink *>(pitem)) {
                    if (link->m_in_port) [[likely]]
                        link->m_in_port->model()->unlink();
                }
            }
            m_selected_items.clear();
        } break;
    }
}

void GraphicsGraph::paint() {
    GraphicsViewWidget::paint();
}

void GraphicsGraph::set_model(GraphModel *model) {
    m_model = model;
    model->view = this;
}

void GraphicsGraph::on_new_node(NodeModel *node_model) {
    auto node = add_child<GraphicsNode>();
    node->set_model(node_model);
}

void GraphicsGraph::on_delete_node(NodeModel *node_model) {
    auto node = static_cast<GraphicsNode *>(node_model->view);
    /* printf("3 %p %p\n", (void *)this, (void *)node); */
    remove_child(node);
}

void GraphicsGraph::on_update(GraphRole role, std::any value) {
    switch (role) {
        case GraphRole::GraphName: {
            (void)std::any_cast<std::string>(value);
        } break;
    }
}

GraphicsGraph::GraphicsGraph() = default;
GraphicsGraph::~GraphicsGraph() = default;

}
